﻿using System;
using System.Collections.Generic;
using System.Reflection;
using BruteForceSerializer.Contract.Internal;

namespace BruteForceSerializer.Normalization
{

    internal static class Normalizer
    {
        public static INormalizedObject Normalize(object objectGraphRoot)
        {
            return (INormalizedObject)Normalize(objectGraphRoot, new Dictionary<object, int>());
        }


        internal static object Normalize(object objectGraphRoot, Dictionary<object, int> visitedObjects)
        {
            if (objectGraphRoot == null) return null;

            int objectId;
            if (Objekt_ist_gecachet(objectGraphRoot, visitedObjects, out objectId))
                return new NormalizedReference(objectId);

            objectId = Objekt_cachen(objectGraphRoot, visitedObjects);
            
            var no = new NormalizedObject(objectGraphRoot.GetType(), objectId);
            {
                if (objectGraphRoot.GetType().IsSimpleType())
                    no.AddField("m_value", Transform_to_value(objectGraphRoot.GetType(), objectGraphRoot, visitedObjects));
                else if (objectGraphRoot.GetType().IsArray)
                        Array_von_Objekten_normalisieren(objectGraphRoot, visitedObjects, no);
                     else
                        Ein_Objekt_normalisieren(objectGraphRoot, visitedObjects, no);
            }
            return no;
        }


        private static bool Objekt_ist_gecachet(object objectGraphRoot, Dictionary<object, int> visitedObjects, out int objectId)
        {
            return visitedObjects.TryGetValue(objectGraphRoot, out objectId);
        }

        private static int Objekt_cachen(object objectGraphRoot, Dictionary<object, int> visitedObjects)
        {
            int objectId = visitedObjects.Count;
            visitedObjects.Add(objectGraphRoot, objectId);
            return objectId;
        }

        private static void Array_von_Objekten_normalisieren(object objectGraphRoot, Dictionary<object, int> visitedObjects, NormalizedObject no)
        {
            no.AddArrayDescription((Array) objectGraphRoot);

            int i = 0;
            foreach (object element in (Array)objectGraphRoot)
                no.AddField(string.Format("[{0}]", i++), Transform_to_value(element.GetType(), element, visitedObjects));
        }

        private static void Ein_Objekt_normalisieren(object objectGraphRoot, Dictionary<object, int> visitedObjects, NormalizedObject no)
        {
            foreach (FieldInfo fi in objectGraphRoot.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                if (!fi.IsNotSerialized)
                    no.AddField(fi.Name, Transform_to_value(fi.FieldType, fi.GetValue(objectGraphRoot), visitedObjects));
        }

        private static object Transform_to_value(Type valueType, object value, Dictionary<object, int> visitedObjects)
        {
            if (valueType == typeof(IntPtr)) return ((IntPtr)value).ToInt64();
            if (valueType.IsSimpleType()) return value;
            return Normalize(value, visitedObjects);
        }

        private static bool IsSimpleType(this Type type)
        {
            return type.IsPrimitive ||
                   type.IsEnum ||
                   type == typeof(string) ||
                   type == typeof(decimal) ||
                   type == typeof(DateTime);
        }
    }
}
